package com.xy.iot.netty.listener;

import com.xy.iot.common.message.BaseMessage;
import com.xy.iot.netty.channel.BaseChannel;
import com.xy.iot.netty.channel.BaseContextChannel;
import com.xy.iot.netty.device.BaseControlCentre;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 类功能说明：自定义数据接收监听器
 * 以下是重写 SimpleChannelInboundHandler 中的一些关键方法及其说明：
 *          channelRead0(ChannelHandlerContext ctx, T msg):
 *                       代替了 channelRead 方法，用于处理具体的消息类型 T，这里的 T 是在构造器中指定的消息类型。Netty 会自动将接收到的消息转换为此类型，并且在处理完成后自动释放消息，简化了资源管理。
 *          exceptionCaught(ChannelHandlerContext ctx, Throwable cause):
 *                      同 ChannelInboundHandlerAdapter，用于捕获并处理在处理过程中发生的异常。
 *          channelActive(ChannelHandlerContext ctx)
 *                      当通道变得活动（即连接已建立）时调用。
 *          channelInactive(ChannelHandlerContext ctx)
 *                    当通道变得不活动（即连接已断开）时调用。
 *                    这些方法与 ChannelInboundHandlerAdapter 相同，可以根据需要重写以响应通道状态变化。
 */
@Slf4j
@Sharable
public class ListenerServerHandler extends SimpleChannelInboundHandler<BaseMessage> {

    private final Map<Integer, Listener> listeners = new ConcurrentHashMap<>();
    @Getter
    private  BaseControlCentre baseControlCentre;

    public ListenerServerHandler(){
    }

    public ListenerServerHandler(BaseControlCentre centre){
        this.baseControlCentre = centre;
    }

    public Listener addListener(int command, Listener listener){
        Listener pre = null;
        if(hasListener(command)){
            pre = listeners.get(command);
        }
        listeners.put(command, listener);
        return pre;
    }

    public Listener removeListener(int command){
        return listeners.remove(command);
    }

    public boolean hasListener(int command){
        return listeners.get(command) != null;
    }

    /**
     * 多个入站出站ChannelHandler的执行顺序
     * 比如：
     *            InboundHandler顺序执行，OutboundHandler逆序执行
     *                 问题：ch.pipeline().addLast(new InboundHandler1());
     *                      ch.pipeline().addLast(new OutboundHandler1());
     *                      ch.pipeline().addLast(new OutboundHandler2());
     *                      ch.pipeline().addLast(new InboundHandler2());
     *                 或者：
     *                      ch.pipeline().addLast(new OutboundHandler1());
     *                      ch.pipeline().addLast(new OutboundHandler2());
     *                      ch.pipeline().addLast(new InboundHandler1());
     *                      ch.pipeline().addLast(new InboundHandler2());
     *             执行顺序是：
     *                     InboundHandler1 channelRead
     *                     InboundHandler2 channelRead
     *                     OutboundHandler2 write
     *                     OutboundHandler1 write
     *     结论：
     *                 1）InboundHandler顺序执行，OutboundHandler逆序执行
     *                 2）InboundHandler之间传递数据，通过ctx.fireChannelRead(msg)
     *                 3）InboundHandler通过ctx.write(msg)，则会传递到outboundHandler
     ***************   4) 使用ctx.write(msg)传递消息，Inbound需要放在结尾，在Outbound之后，不然outboundhandler会不执行；
     *                     但是使用channel.write(msg)、pipline.write(msg)情况会不一致，都会执行
     *                 5) outBound和Inbound谁先执行，针对客户端和服务端而言，客户端是发起请求再接受数据，先outbound再inbound，服务端则相反
     * @param ctx   信道上下文 IN
     * @param msg   解析的数据信息
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, BaseMessage msg) throws Exception {
        Listener listener = listeners.get(msg.getCommand());
        if(listener != null){
            BaseChannel channel = baseControlCentre == null ? new BaseContextChannel(ctx) : baseControlCentre.get(msg.getDeviceGroup());
            listener.receive(channel == null ? new BaseContextChannel(ctx) : channel, msg);
        }else {
            log.warn("没有找到对应的监听器，command:{}，接收到消息：{}", msg.getCommand(), msg);
            //当没有找到对于得业务时，返回发送过来的数据,进入到下一个处理器去
            ctx.write(msg);
        }
    }

}
